// Copyright Epic Games, Inc. All Rights Reserved.

#include <zencore/uid.h>

#include <zencore/endian.h>
#include <zencore/string.h>
#include <zencore/testing.h>

#include <atomic>
#include <bit>
#include <chrono>
#include <random>
#include <set>
#include <unordered_map>

namespace zen {

//////////////////////////////////////////////////////////////////////////

namespace detail {
	static bool					OidInitialised;
	static uint32_t				RunId;
	static std::atomic_uint32_t Serial;
}  // namespace detail

//////////////////////////////////////////////////////////////////////////

const Oid Oid::Zero = {{0u, 0u, 0u}};
const Oid Oid::Max	= {{~0u, ~0u, ~0u}};

void
Oid::Initialize()
{
	if (!detail::OidInitialised)
	{
		std::random_device Rng;
		detail::RunId  = Rng();
		detail::Serial = Rng();

		detail::OidInitialised = true;
	}
}

const Oid&
Oid::Generate()
{
	if (!detail::OidInitialised)
	{
		Oid::Initialize();
	}

	const uint64_t kOffset = 1'609'459'200;	 // Seconds from 1970 -> 2021
	const uint64_t Time	   = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) - kOffset;

	OidBits[0] = ToNetworkOrder(uint32_t(Time));
	OidBits[1] = ToNetworkOrder(uint32_t(++detail::Serial));
	OidBits[2] = detail::RunId;

	return *this;
}

Oid
Oid::NewOid()
{
	return Oid().Generate();
}

Oid
Oid::FromHexString(const std::string_view String)
{
	ZEN_ASSERT(String.size() == 2 * sizeof(Oid::OidBits));

	Oid Id;

	if (ParseHexBytes(String.data(), String.size(), reinterpret_cast<uint8_t*>(Id.OidBits)))
	{
		return Id;
	}
	else
	{
		return Oid::Zero;
	}
}

Oid
Oid::TryFromHexString(const std::string_view String, const Oid& Default)
{
	if (String.length() != StringLength)
	{
		return Default;
	}

	Oid Id;

	if (ParseHexBytes(String.data(), String.size(), reinterpret_cast<uint8_t*>(Id.OidBits)))
	{
		return Id;
	}
	else
	{
		return Default;
	}
}

Oid
Oid::FromMemory(const void* Ptr)
{
	Oid Id;
	memcpy(Id.OidBits, Ptr, sizeof Id);
	return Id;
}

void
Oid::ToString(char OutString[StringLength]) const
{
	ToHexBytes(reinterpret_cast<const uint8_t*>(OidBits), sizeof(Oid::OidBits), OutString);
	OutString[StringLength] = '\0';
}

std::string
Oid::ToString() const
{
	char OutString[StringLength + 1];
	ToString(OutString);
	return std::string(OutString, StringLength);
}

StringBuilderBase&
Oid::ToString(StringBuilderBase& OutString) const
{
	String_t Str;
	ToHexBytes(reinterpret_cast<const uint8_t*>(OidBits), sizeof(Oid::OidBits), Str);

	OutString.AppendRange(Str, &Str[StringLength]);

	return OutString;
}

#if ZEN_WITH_TESTS

TEST_CASE("Oid")
{
	SUBCASE("Basic")
	{
		Oid id1 = Oid::NewOid();
		ZEN_UNUSED(id1);

		std::vector<Oid>						  ids;
		std::set<Oid>							  idset;
		std::unordered_map<Oid, int, Oid::Hasher> idmap;

		const int Count = 1000;

		for (int i = 0; i < Count; ++i)
		{
			Oid id;
			id.Generate();

			ids.emplace_back(id);
			idset.insert(id);
			idmap.insert({id, i});
		}

		CHECK(ids.size() == Count);
		CHECK(idset.size() == Count);  // All ids should be unique
		CHECK(idmap.size() == Count);  // Ditto
	}
}

void
uid_forcelink()
{
}

#endif

}  // namespace zen
